package com.zcsy.commons.upload;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Title: BinaryDataUtil.java
 * @Package： com.zcsy.commons.upload 
 * @Description: 二进制数据存取工具类.
 * @author tanhuatang 786468449@qq.com
 * @Copyright: 2015 武汉中财数元信息技术有限公司 All rights reserved.
 * @date 2016年1月18日
 * @version 1.0
 * @see JDK1.7.0_75
 * @since
 */
public final class BinaryDataUtil {

    /** 字节缓存大小. */
    private static final int BUF_SIZE = 1024;

    /** 私有化. */
    private BinaryDataUtil() {

    }

    /**
     * @Description 读取文件内容<br>
     * @param file 文件对象
     * @return 字节数组
     * @author tanhuatang 786468449@qq.com
     * @date 2016年1月18日
     * @see
     */
    public static byte[] readFileData(final File file) {
        byte[] ret = null;
        FileInputStream fis = null;
        try {
            //获取文件输入流
            fis = new FileInputStream(file);
            //获取文件字节内容
            ret = new byte[fis.available()];
            final int count = fis.read(ret);
            if (count != ret.length) {
                throw new RuntimeException("读取文件内容不完整");
            }
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            }
            throw new RuntimeException(e);
        } finally {
            close(fis);
        }
        return ret;
    }

    /**
     * 读取流.
     * 
     * @param in
     *            流
     * @return 数据
     * @throws IOException
     *             读取出现异常
     */
    public static byte[] toByteArray(final InputStream in) throws IOException {
        if (in == null) {
            return null;
        }
        final ByteBuffer buf = ByteBuffer.allocate(in.available());

        final byte[] tmp = new byte[BUF_SIZE];
        for (int cnt = in.read(tmp); cnt != -1; cnt = in.read(tmp)) {
            buf.put(tmp, 0, cnt);
        }

        return buf.array();
    }

    /**
     * 把文件中的数据写入到Http响应.
     * 
     * @param dataType
     *            数据类型，即文件名称后缀
     * @param file
     *            文件
     * @param request
     *            http请求
     * @param response
     *            httpResponse
     */
    public static void writeToStream(final String dataType, final File file,
                                     final HttpServletRequest request,
                                     final HttpServletResponse response) {
        fileToResponse(dataType, file, request, response, false);
    }

    /**
     * 下载文件..
     * 
     * @param dataType
     *            数据类型，即文件名称后缀
     * @param file
     *            文件
     * @param request
     *            http请求
     * @param response
     *            httpResponse
     */
    public static void download(final String dataType, final File file,
                                final HttpServletRequest request,
                                final HttpServletResponse response) {
        fileToResponse(dataType, file, request, response, false);
    }

    /**
     * 把文件中的数据写入到Http响应.
     * 
     * @param filename
     *            文件名称
     * @param file
     *            文件
     * @param request
     *            http请求
     * @param response
     *            httpResponse
     * @param isDownload
     *            是否添加文件下载的header
     */
    private static void fileToResponse(final String filename, final File file,
                                       final HttpServletRequest request,
                                       final HttpServletResponse response, final boolean isDownload) {
        if (!file.exists()) {
            return;
        }
        if (isDownload) {
            addDownloadHeader(request, response, filename);
        }
        addLengthHeader(response, (int)file.length());
        response.setContentType(request.getSession().getServletContext().getMimeType(filename));
        FileInputStream fis = null;
        ServletOutputStream sos = null;
        try {
            fis = new FileInputStream(file);
            sos = response.getOutputStream();
            final byte[] buffer = new byte[BUF_SIZE];
            int readCount = 0;
            while ((readCount = fis.read(buffer)) > 0) {
                sos.write(buffer, 0, readCount);
            }
        }
        catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            close(fis);
            close(sos);
        }
    }

    /**
     * 把二进数据写入到Http响应流.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     */
    public static void writeToStream(final String filename, final byte[] data,
                                     final HttpServletRequest request,
                                     final HttpServletResponse response) {
        dataToResponse(filename, data, request, response, false);
    }

    /**
     * 把二进数据作为文件进行下载.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     */
    public static void download(final String filename, final byte[] data,
                                final HttpServletRequest request,
                                final HttpServletResponse response) {
        dataToResponse(filename, data, request, response, true);
    }

    /**
     * 把二进数据写入到Http响应流.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     * @param isDownload
     *            是否添加文件下载的header
     */
    private static void dataToResponse(final String filename, final byte[] data,
                                       final HttpServletRequest request,
                                       final HttpServletResponse response, final boolean isDownload) {
        if (data == null || data.length == 0) {
            return;
        }
        if (isDownload) {
            addDownloadHeader(request, response, filename);
        }
        addLengthHeader(response, data.length);
        response.setContentType(request.getSession().getServletContext().getMimeType(filename));
        ServletOutputStream sos = null;
        try {
            sos = response.getOutputStream();
            sos.write(data);
        }
        catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            close(sos);
        }
    }

    /**
     * 把二进数据写入到Http响应流.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     */
    public static void writeToStream(final String filename, final InputStream data,
                                     final HttpServletRequest request,
                                     final HttpServletResponse response) {
        dataToResponse(filename, data, request, response, false);
    }

    /**
     * 把二进数据作为文件进行下载.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     */
    public static void download(final String filename, final InputStream data,
                                final HttpServletRequest request,
                                final HttpServletResponse response) {
        dataToResponse(filename, data, request, response, true);
    }
    
    /**
     * 把二进数据写入到Http响应流.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     * @param isDownload
     *            是否添加文件下载的header
     */
    private static void dataToResponse(final String filename, final InputStream data,
                                       final HttpServletRequest request,
                                       final HttpServletResponse response, final boolean isDownload) {
        if (data == null) {
            return;
        }
        if (isDownload) {
            addDownloadHeader(request, response, filename);
        }
        response.setContentType(request.getSession().getServletContext().getMimeType(filename));
        ServletOutputStream sos = null;
        try {
            sos = response.getOutputStream();
            final byte[] buffer = new byte[BUF_SIZE];
            int readCount = 0;
            while ((readCount = data.read(buffer)) != -1) {
                sos.write(buffer, 0, readCount);
            }
        }
        catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            close(sos);
        }
    }

    /**
     * 把二进数据写入到Http响应流.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     */
    public static void writeToStream(final Map<String, Object> fileInfo, final InputStream data,
                                     final HttpServletRequest request,
                                     final HttpServletResponse response) {
        dataToResponse(fileInfo, data, request, response, false);
    }

    /**
     * 把二进数据作为文件进行下载.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     */
    public static void download(final Map<String, Object> fileInfo, final InputStream data,
                                final HttpServletRequest request,
                                final HttpServletResponse response) {
        dataToResponse(fileInfo, data, request, response, true);
    }

    /**
     * 把二进数据写入到Http响应流.
     * 
     * @param filename
     *            数据类型，即文件名称后缀
     * @param data
     *            大字段
     * @param request
     *            http请求
     * @param response
     *            数据流
     * @param isDownload
     *            是否添加文件下载的header
     */
    private static void dataToResponse(final Map<String, Object> fileInfo, final InputStream data,
                                       final HttpServletRequest request,
                                       final HttpServletResponse response, final boolean isDownload) {
        if (data == null) {
            return;
        }
        final String filename = (String)fileInfo.get("filename");
        if (isDownload) {
            addDownloadHeader(request, response, filename);
        }
        response.setContentType(request.getSession().getServletContext().getMimeType(filename));
        ServletOutputStream sos = null;
        try {
            final Long length = (Long)fileInfo.get("length");
            if (length != null) {
                addLengthHeader(response, length);
            }
            else if (data.available() > 0) {
                addLengthHeader(response, data.available());
            }

            sos = response.getOutputStream();
            final byte[] buffer = new byte[BUF_SIZE];
            int readCount = 0;
            while ((readCount = data.read(buffer)) > 0) {
                sos.write(buffer, 0, readCount);
            }
        }
        catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            close(sos);
        }
    }

    /**
     * 关闭.
     * 
     * @param closable
     *            可关闭对象
     */
    public static void close(final Closeable closable) {
        try {
            if (closable != null) {
                closable.close();
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 添加文件下载的Header.
     * 
     * @param request
     *            Http请求
     * @param response
     *            Http响应
     * @param filename
     *            下载显示的文件名
     */
    public static void addDownloadHeader(final HttpServletRequest request,
                                         final HttpServletResponse response, final String filename) {
        try {
            response.setHeader("Content-Disposition",
                "attachment;filename=\"" + encodeFilenameForDownload(request, filename) + "\"");
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对下载的文件名进行编码处理
     * 
     * @param request
     * @param filename
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String encodeFilenameForDownload(final HttpServletRequest request,
                                                   final String filename) throws UnsupportedEncodingException {

        final String userAgent = request.getHeader("User-Agent");
        if (userAgent == null || userAgent.indexOf("Trident") == -1) {
            return new String(filename.getBytes(), "iso8859-1");
        }
        // 针对IE 进行不同的处理
        return URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
    }

    /**
     * 添加文件下载的Header.
     * 
     * @param response
     *            Http响应
     * @param size
     *            大小
     */
    private static void addLengthHeader(final HttpServletResponse response, final long size) {
        response.addHeader("Content-Length", size + "");
    }
}
